<?php
/**
 * Created by PhpStorm.
 * User: simon
 * Date: 2016/9/13
 * Time: 23:00
 */

namespace p5\objects\tree;


class TreeNode
{

    var $id;
    var $name;
    var $parentId;
    var $parentNode;
    var $icon;
    var $showChapterNo = false;
    var $showCheckBox = false;
    var $clickMethod = "";
    var $children = array();
    var $lines = array();
    var $checked = false;
    var $rank = 0;
    var $sequence = 0;
    var $subSequence = 0;
    var $chapterNo = "";
    var $shadowIndex;
    var $display = 'Y';
    var $open = 'Y';
    var $openFolderIcon = "../../p5/tree/folderOpen.png";
    var $closeFolderIcon = "../../p5/tree/folderOpen.png";
    var $attributes = array();

    public function __construct($id, $name, $parentId = null, $icon = "")
    {
        $this->id = $id;
        $this->name = $name;
        $this->parentId = $parentId;
        $this->icon = $icon;
        $this->open = "Y";
        $this->display = "Y";
    }

    public function refreshChapterNo()
    {
        if ($this->parentNode == null) {
            $this->chapterNo = "";
        } else {
            $temp = $this->parentNode->chapterNo;
            if ($temp == '') {
                $this->chapterNo = $this->subSequence + 1;
            } else {
                $this->chapterNo = $temp . "." . ($this->subSequence + 1);
            }
        }
    }

    public function refreshRank()
    {
        if ($this->parentNode == null || $this->parentId == "") {
            $this->rank = 0;
        } else {
            $this->rank = ($this->parentNode->rank + 1);
        }
    }

    public function findAncestorById($ancestorId)
    {
        if ($this->parentNode == null || $this->parentId == "") {
            return null;
        }
        $node = $this->parentNode;
        if ($node->id == $ancestorId) {
            return $node;
        } else {
            return $node->findAncestorById($ancestorId);
        }
    }

    public function hasChild()
    {
        return count($this->children) > 0;
    }

    public function hasElderBrother()
    {
        return $this->subSequence > 0;
    }

    public function hasYoungerBrother()
    {
        if ($this->parentNode == null || $this->parentId == "") {
            return false;
        }
        $c = count($this->parentNode->children) - 1;
        return $c > $this->subSequence;
    }

    public function getChildren()
    {
        return $this->children;
    }

    public function addChild($node)
    {
        $node->subSequence = count($this->children);
        $node->parentId = $this->id;
        $node->parentNode = $this;
        $node->rank = $this->rank + 1;
        array_push($this->children, $node);
    }

    public function removeChild($id)
    {
        $matrix = array();
        $size = $this->childrenSize();
        $begin = false;
        $child = "";
        for ($i = 0; $i < $size; $i++) {
            $node = $this->children[$i];
            if ($node->id == $id) {
                $child = $node;
                $begin = true;
                continue;
            }
            if ($begin) {
                $node->subSequence--;
            }
            array_push($matrix, $node);
        }
        $this->children = $matrix;
        unset($child);
    }

    public function childrenSize()
    {
        return count($this->children);
    }

    public function nextSibling()
    {
        if ($this->hasYoungerBrother()) {
            return $this->parentNode->children[$this->subSequence + 1];
        }
        return null;
    }

    public function getDebugName()
    {
        return "id=" . $this->id . ",name=" . $this->name . ",parentId=" . $this->parentId . ",rank=" . $this->rank . ",sequence=" . $this->sequence . ",subSequence=" . $this->subSequence . ",display=" . $this->display . ",open=" . $this->open;
    }

    public function getName()
    {
        if ($this->showChapterNo) {
            return $this->chapterNo . ". " . $this->name;
        }
        return $this->name;
    }

    public function changeOpenStatus()
    {
        if ($this->open == "Y") {
            $this->open = "N";
        } else {
            $this->open = "Y";
        }
    }

    public function getIcon()
    {
        if ($this->icon != "") {
            return $this->icon;
        }
        if ($this->isOpen()) {
            return $this->openFolderIcon;
        }
        return $this->closeFolderIcon;
    }

    public function isOpen()
    {
        return $this->open == "Y";
    }

    public function isDisplay()
    {
        return $this->display == "Y";
    }

    public function setAttribute($name, $value)
    {
        $this->attributes[$name] = $value;
    }

    public function getAttribute($name, $defaultValue = "")
    {
        if (array_key_exists($name, $this->attributes)) {
            return $this->attributes[$name];
        }
        return $defaultValue;
    }

    public function removeAttribute($name, $defaultValue = "")
    {
        if (array_key_exists($name, $this->attributes)) {
            unset($this->attributes[$name]);
        }
    }

    public function refreshDisplay()
    {

    }

    public function toArray()
    {
        $me = ["id" => $this->id, "name" => $this->name, "parentId" => $this->parentId];
        $me = array_merge($me, $this->attributes);
        $children = [];
        foreach ($this->children as $child) {
            array_push($children, $child->toArray());
        }
        $me["children"] = $children;
        return $me;
    }

    /**
     * @return mixed
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @param mixed $id
     */
    public function setId($id)
    {
        $this->id = $id;
    }

    /**
     * @return null
     */
    public function getParentId()
    {
        return $this->parentId;
    }

    /**
     * @param null $parentId
     */
    public function setParentId($parentId)
    {
        $this->parentId = $parentId;
    }

    /**
     * @return string
     */
    public function getChapterNo()
    {
        return $this->chapterNo;
    }

    /**
     * @param string $chapterNo
     */
    public function setChapterNo($chapterNo)
    {
        $this->chapterNo = $chapterNo;
    }

    /**
     * @return int
     */
    public function getSequence()
    {
        return $this->sequence;
    }

    /**
     * @param int $sequence
     */
    public function setSequence($sequence)
    {
        $this->sequence = $sequence;
    }


}

class Tree extends TreeNode
{

    var $root;
    var $displyDegree = 2;
    var $inited = false;
    var $currentFocusNodeId = "";
    var $nodeMap = array();
    var $tempMap = array();
    var $nodeList = array();
    var $pointer = null;
    var $rootSelectable = false;


    public function __construct($id, $name, $icon = "", $displyDegree = 100)
    {
        $this->displyDegree = $displyDegree;
        $this->root = new TreeNode($id, $name, null, $icon);
        $this->nodeMap[$id] = $this->root;
    }

    public function getRoot()
    {
        return $this->root;
    }

    public function size()
    {
        return count($this->nodeList);
    }

    public function add($node)
    {
        if (array_key_exists($node->id, $this->nodeMap)) {
            return;
        }
        $node->showChapterNo = $this->showChapterNo;
        $parent = $this->findNodeById($node->parentId);

        if (empty($parent)) {
            $parent = $this->root;
        }

        if (!empty($parent)) {
            $parent->addChild($node);
            $this->nodeMap[$node->id] = $node;
            return;
        }
        $this->tempMap[$node->id] = $node;
    }

    public function addAndRefresh($node)
    {
        $this->add($node);
        $this->init();
    }

    public function init()
    {
        $this->pointer = null;
        $this->sortNodes();
        $this->initSequence();
        $this->initShadowIndex();
        $this->initLines();
        $this->inited = true;
    }

    public function sortNodes()
    {
        while (count($this->tempMap) > 0) {
            $matrix = array();
            while (list($id, $node) = each($this->tempMap)) {
                $parent = $this->findNodeById($node->parentId);
                if ($parent != null) {
                    $this->add($node);
                    continue;
                } else if (!array_key_exists($node->parentId, $this->tempMap)) {
                    $node->parentId = $this->root->id;
                    $this->add($node);
                    continue;
                } else {
                    $matrix[$id] = $node;
                }
            }
            $this->tempMap = $matrix;
        }
    }

    public function initSequence()
    {
        $this->nodeList = array();
        $this->nodeMap = array();

        $index = 0;
        $node = $this->goNext();
        while ($node != null) {
            $node->sequence = $index++;
            $node->refreshChapterNo();
            $node->refreshRank();
            array_push($this->nodeList, $node);
            $this->nodeMap[$node->id] = $node;

            if (!$this->inited) {
                $node->open = ($node->rank < $this->displyDegree) ? "Y" : "N";
            }
            $node->refreshChapterNo();
            $node->refreshDisplay();
            $node->lines = array();
            $node = $this->goNext();
        }
    }

    public function goNext()
    {
        if ($this->pointer == null) {
            $this->pointer = $this->root;
        } else {
            if ($this->pointer->childrenSize() > 0) {
                $this->pointer = $this->pointer->children[0];
            } else {
                if ($this->pointer->parentNode == null) {
                    return null;
                } else if ($this->pointer->nextSibling() != null) {
                    $this->pointer = $this->pointer->nextSibling();
                } else {
                    $parentNode = $this->pointer->parentNode;
                    $brother = $parentNode->nextSibling();

                    while ($parentNode != null && $brother == null) {
                        $brother = $parentNode->nextSibling();
                        $parentNode = $parentNode->parentNode;
                    }
                    $this->pointer = $brother;
                }
            }
        }
        return $this->pointer;
    }

    public function initShadowIndex()
    {
        $size = $this->size();
        for ($i = 0; $i < $size; $i++) {
            $node = $this->nodeList[$i];
            $node->shadowIndex = $node->sequence;
            for ($j = ($node->sequence + 1); $j < $size; $j++) {
                $temp = $this->nodeList[$j];
                if ($node->rank < $temp->rank) {
                    $node->shadowIndex = $temp->sequence;
                } else {
                    break;
                }
            }
        }
    }

    public function initLines()
    {
        $size = $this->size();
        for ($i = 0; $i < $size; $i++) {
            $node = $this->nodeList[$i];
            $line = $node->hasYoungerBrother();
            for ($j = $node->sequence + 1; $j <= $node->shadowIndex; $j++) {
                $temp = $this->nodeList[$j];
                array_push($temp->lines, $line);
            }
        }
    }

    public function expandAll()
    {
        $size = $this->size();
        for ($i = 0; $i < $size; $i++) {
            $node = $this->nodeList[$i];
            $node->open = "Y";
            $node->display = "Y";
        }
    }

    public function closeAll()
    {
        $size = $this->size();
        for ($i = 1; $i < $size; $i++) {
            $node = $this->nodeList[$i];
            $node->open = "N";
            $node->display = "N";
        }
    }

    public function changeNodeOpenStatus($id)
    {
        $node = $this->findNodeById($id);

        if ($node != null) {
            $node->changeOpenStatus();
            $size = $this->size();

            for ($j = ($node->sequence + 1); $j < $size; $j++) {
                $temp = $this->nodeList[$j];
                if ($temp->rank <= $node->rank) {
                    break;
                }
                $temp->refreshDisplay();
            }

        } else {
            // echo "no node ".$id ;
        }
    }

    public function changeNodeName($id, $name)
    {
        $node = $this->findNodeById($id);
        if ($node != null) {
            $node->name = $name;
        }
    }

    public function removeNode($id)
    {
        $node = $this->findNodeById($id);
        if ($node == null) {
            return;
        }
        $parent = $node->parentNode;
        if ($parent != null) {
            $parent->removeChild($node->id);
        }

        $node->parentId = "";
        $node->parentNode = null;
        $this->init();
    }

    public function resetNodeParent($id, $parentId)
    {
        $node = $this->findNodeById($parentId);
        $temp = $node->findAncestorById($id);
        if ($temp != null) {
            // echo "can move ancestor under offspring";
            return false;
        }

        $node = $this->findNodeById($id);
        if ($node == null) {
            return false;
        }
        $this->removeNode($id);

        $node->parentId = $parentId;
        $parent = $this->findNodeById($parentId);
        $node->parentNode = $parent;

        $this->add($node);

        $this->init();
        return true;
    }

    public function findMeAndMyAncestors($id)
    {
        $matrix = array();
        $node = $this->findNodeById($id);
        while ($node != null) {
            array_push($matrix, $node->id);
            $node = $node->parentNode;
        }
        return $matrix;
    }

    public function findMyTopAncestorExceptRoot($id)
    {
        $node = $this->findNodeById($id);
        while ($node != null) {
            if ($node->parentId == $this->root->id) {
                break;
            }
            $node = $node->parentNode;
        }
        return $node;
    }

    public function findMeAndMyOffsprings($id)
    {
        $matrix = array();

        $size = $this->size();
        $node = $this->findNodeById($id);
        if ($node != null) {
            array_push($matrix, $node->id);
            for ($j = ($node->sequence + 1); $j < $size; $j++) {
                $temp = $this->nodeList[$j];
                if ($temp->rank <= $node->rank) {
                    break;
                }
                array_push($matrix, $temp->id);
            }
        }
        return $matrix;
    }

    public function findNodeById($id)
    {
        if (array_key_exists($id, $this->nodeMap)) {
            return $this->nodeMap[$id];
        }
        return null;
    }

    public function setRootSelectable($selectable)
    {
        $this->rootSelectable = $selectable;
    }

    function _debug()
    {
        print_html("-----------begin-------------");
        $list = array_values($this->nodeMap);
        for ($i = 0; $i < count($list); $i++) {
            $node = $list[$i];
            $this->_debug_node($node);
        }
        print_html("-----------end---------------");
    }

    function _debug_node($node)
    {
        print_html($node->id . "-" . $node->name . ",parent:" . $node->parentId);
    }

    public function toArray()
    {
        return $this->root->toArray();
    }

    /**
     * @return array
     */
    public function getNodeMap()
    {
        return $this->nodeMap;
    }

    /**
     * @param array $nodeMap
     */
    public function setNodeMap($nodeMap)
    {
        $this->nodeMap = $nodeMap;
    }

    /**
     * @return array
     */
    public function getNodeList()
    {
        return $this->nodeList;
    }

    /**
     * @param array $nodeList
     */
    public function setNodeList($nodeList)
    {
        $this->nodeList = $nodeList;
    }


}
